namespace How_toEval;

public class DefineScanner
{
    public Dictionary<AbstractSyntaxTree, HashSet<string>> Scopes(AbstractSyntaxTree exp)
    {
        ResolveExpression(exp);
        return scopes;
    }

    public DefineScanner()
    {
        scopes = new Dictionary<AbstractSyntaxTree, HashSet<string>>(comparer: ReferenceEqualityComparer.Instance);
        ScopeStack = new LinkedList<HashSet<string>>();
        ScopeStack.AddFirst(builtinScope);
        ScopeStack.AddFirst(globalScope);
    }

    LinkedList<HashSet<string>> ScopeStack { get; set; }
    public HashSet<string> GlobalScope { get => globalScope; }
    HashSet<string> globalScope = new HashSet<string>();
    public HashSet<string> BuiltinScope { get => builtinScope; }
    HashSet<string> builtinScope = Builtins.BuiltinScope;

    private void EnterScope()
    {
        ScopeStack.AddFirst(new HashSet<string>());
    }

    private void ExitScope()
    {
        ScopeStack.RemoveFirst();
    }

    private void Declare(string variable)
    {
        ScopeStack.First().Add(variable);
    }

    public void ResolveExpression(AbstractSyntaxTree exp)
    {
        switch (exp)
        {
            case AbstractSyntaxTree.Group g: ResolveGroup(g); break;
            case AbstractSyntaxTree.Var v: ResolveVar(v); break;
            case AbstractSyntaxTree.Call c: ResolveCall(c); break;
            case AbstractSyntaxTree.Define d: ResolveDefine(d); break;
            case AbstractSyntaxTree.Function f: ResolveFunction(f); break;
            case AbstractSyntaxTree.Integer i: ResolveInteger(i); break;
            case AbstractSyntaxTree.BinaryOperateNode b: ResolveBinaryNode(b); break;
            case AbstractSyntaxTree.AnnotatedExpression a: ResolvedAnnotated(a); break;
            case AbstractSyntaxTree.Float _: break;
            default: throw new Exception($"SemanticScanner cannot handle such type: {exp.GetType()}");
        }
    }


    private void ResolvedAnnotated(AbstractSyntaxTree.AnnotatedExpression a)
    {
        ResolveExpression(a.Expression);
    }

    private void ResolveBinaryNode(AbstractSyntaxTree.BinaryOperateNode b)
    {
        ResolveExpression(b.Left);
        ResolveExpression(b.Right);
    }

    private void ResolveInteger(AbstractSyntaxTree.Integer i)
    {
        return;
    }

    private void ResolveFunction(AbstractSyntaxTree.Function f)
    {
        WithNewScope(() =>
        {
            foreach (var param in f.Params)
            {
                Declare(param.Item1);
            }
            ResolveExpression(f.Body);
            scopes.Add(f, ScopeStack.First());
        });

    }

    private void WithNewScope(Action action)
    {
        EnterScope();
        action();
        ExitScope();
    }

    private void ResolveDefine(AbstractSyntaxTree.Define d)
    {
        Declare(d.Name);
        ResolveExpression(d.Value);
    }

    private void ResolveCall(AbstractSyntaxTree.Call c)
    {
        foreach (var arg in c.Arguments)
        {
            ResolveExpression(arg);
        }

        ResolveExpression(c.Called);
    }

    private void ResolveVar(AbstractSyntaxTree.Var v)
    {
        return;
    }

    private void ResolveGroup(AbstractSyntaxTree.Group g)
    {
        foreach (var exp in g.Leaves)
        {
            ResolveExpression(exp);
        }
    }

    Dictionary<AbstractSyntaxTree, HashSet<string>> scopes;
}
